(* Functional helper functions *)

	fun flatten [] = []
	 |  flatten (x::xs) = x @ flatten xs;
	 
	fun g (xs,ys) = flatten( map (fn x => (map (fn y => x::y) ys) ) xs );
	
		(* NOTE
			A slightly more readable but less fun version of g would be:
			
			fun g ([],ys) = []
			 |  g (x::xs,ys) = (map (fn y => x::y) ys) @ (g xs ys);		 
		*)
	
	fun allseqs xss = foldr g [[]] xss;
	
		(* samples:
		
			- allseqs [["a","b"],["u","v"]];
			> val it = [["a", "u"], ["a", "v"], ["b", "u"], ["b", "v"]] : string list list
			
			- allseqs[["a","b"],["u","v"],["x","y","z"]];
			> val it =
				[["a", "u", "x"], ["a", "u", "y"], ["a", "u", "z"], ["a", "v", "x"],
				 ["a", "v", "y"], ["a", "v", "z"], ["b", "u", "x"], ["b", "u", "y"],
				 ["b", "u", "z"], ["b", "v", "x"], ["b", "v", "y"], ["b", "v", "z"]] :
			  string list list
		
		*)

(* IO/String helper functions *)

	exception debugAssertionFailed of int;

	fun getLineString(inStream) =
		let
			val lineStringWithTerminator = TextIO.inputLine(inStream);
			val lineString = String.substring(lineStringWithTerminator, 0, String.size(lineStringWithTerminator)-1);
				(* gets rid of newline character *)
		in
			lineString
		end;
		
	val separatorChar = #"@";
	fun isSeparator x = (x=separatorChar);
		
	fun getFields(fieldsString) = String.fields isSeparator fieldsString;
		
	fun getParamLists inStream 0 = []
	 |  getParamLists inStream n = getFields(getLineString(inStream)) :: getParamLists inStream (n-1);
		(* TODO assert inStream empty? *)
	
	fun formatDoublesForPrint s = String.implode (map (fn c => if (c=(#".")) then (#"p") else c) (String.explode s));
	
	val paramSeparatorString = "Z";
	fun paramPrettyStrings [] = []
	 |  paramPrettyStrings (x::xs) = (formatDoublesForPrint x)::(paramSeparatorString::(paramPrettyStrings xs));
	 
	(* 
		fun paramPrettyStrings [] paramCombination = if (paramCombination=[]) then [] else raise debugAssertionFailed(~1)
		 |  paramPrettyStrings paramNames [] = if (paramNames=[]) then [] else raise debugAssertionFailed(~2)
		 |  paramPrettyStrings (pn::pns) (p::pc) = ( pn^(formatDoublesForPrint p) )::paramPrettyStrings pns pc;
	
	*)
	
	
	fun interleave xs [] = xs
	 |  interleave [] ys = ys
	 |	interleave (x::xs) (y::ys) = x::(y::(interleave xs ys));
	 
	fun writeList outStream [] = ()
	 | writeList outStream (x::xs) = (TextIO.output(outStream,x) ; writeList outStream xs );
	
	(* template specific print helper functions *)
	
		fun mergeStructureAndCombinations structureFields combinations =
				map (fn combination => interleave structureFields combination)  combinations;
		
		(* returns flat string list *)
		fun getCasesFromInitCalls(className, templateCaseStatementFields, initCalls) =
			let
				fun getCasesHelper [] i = if (i=length(initCalls)) then [] else raise debugAssertionFailed(~3)
				 |  getCasesHelper (call::calls) i =
					[ List.nth(templateCaseStatementFields,0), Int.toString(i),
					List.nth(templateCaseStatementFields,1), className,
					List.nth(templateCaseStatementFields,2) ] @ call @
					[ List.nth(templateCaseStatementFields,3) ] @ (getCasesHelper calls (i+1))
			in
				getCasesHelper initCalls 0
			end;
		
		fun getCases( className, templateCaseStatementFields, initCallFields, paramValueCombinations) =
			getCasesFromInitCalls( className, templateCaseStatementFields, mergeStructureAndCombinations initCallFields paramValueCombinations);
		
		fun getTests( className, paramNameFields, templateTestFields, paramValueCombinations, videoList ) =
			let
				fun getTestHelper video paramCombination i = 
					[ List.nth(templateTestFields,0), className,
					List.nth(templateTestFields,1) ] @ ( paramPrettyStrings paramCombination ) @ [video, 
					List.nth(templateTestFields,2) ] @ ( paramPrettyStrings paramCombination ) @ [video,
					List.nth(templateTestFields,3), Int.toString(i),
					List.nth(templateTestFields,4), video,
					List.nth(templateTestFields,5)
					];
				val n = length(paramValueCombinations);
				fun getTestsHelper [] _ _ = []
				 |  getTestsHelper (v::vs) [] i =
				 	if (i=n) then
				 		getTestsHelper vs paramValueCombinations 0
				 	else
				 		raise debugAssertionFailed(~4)
				 |  getTestsHelper (v::vs) (c::cs) i = (getTestHelper v c i) @ getTestsHelper (v::vs) cs (i+1)
			in
				getTestsHelper videoList paramValueCombinations 0
			end;
			
		(*
					fun getTests( className, paramNameFields, templateTestFields, paramValueCombinations, videoList ) =
						let
							fun getTestHelper video paramCombination i = 
								[ List.nth(templateTestFields,0), className,
								List.nth(templateTestFields,1) ] @ ( paramPrettyStrings paramNameFields paramCombination ) @ [video, 
								List.nth(templateTestFields,2) ] @ ( paramPrettyStrings paramNameFields paramCombination ) @ [video,
								List.nth(templateTestFields,3), Int.toString(i),
								List.nth(templateTestFields,4), video,
								List.nth(templateTestFields,5)
								];
							val n = length(paramValueCombinations);
							fun getTestsHelper [] _ _ = []
							 |  getTestsHelper (v::vs) [] i =
								if (i=n) then
									getTestsHelper vs paramValueCombinations 0
								else
									raise debugAssertionFailed(~4)
							 |  getTestsHelper (v::vs) (c::cs) i = (getTestHelper v c i) @ getTestsHelper (v::vs) cs (i+1)
						in
							getTestsHelper videoList paramValueCombinations 0
						end;
		*)
		
	(* debugging *)
	
		fun printList xs = ( TextIO.print("[") ; map (fn x => TextIO.print (x ^ "@")) xs; TextIO.print("]\n") ) ;
		
(* Getting template for class, parameters, and video test files *)
	val args = CommandLine.arguments();
	val outputFileName = List.nth(args,0);
	val templateName = List.nth(args,1);
	val videoListName = List.nth(args,2);
	val genFileName = List.nth(args,3);
	(*  WARNING: HORRIBLE HACK! The third argument is not used. It is only used by the project makefile to create a useful dependency *)


(* Preparing template *)
	val templateFileInStream = TextIO.openIn(templateName);
	
	val templateString = TextIO.inputAll(templateFileInStream);
	
	val templateFields = getFields(templateString);
	
	val templateFromCasesAndOnFields = List.drop(templateFields, 4);
		(* First drop the first four fields, which are not part of of the case statement. *)
	val templateCaseStatementFields = List.take( templateFromCasesAndOnFields, 4);
		(* Then take the first four remaining fields, which constitute the case statement. *)
		
	val templateAfterCasesFields = List.drop( templateFromCasesAndOnFields, 4);
	
	val templateBetweenCasesAndTestsField = hd templateAfterCasesFields;
	val templateTestFields = tl templateAfterCasesFields;


	val genFileInStream = TextIO.openIn(genFileName);
	
	val className = getLineString(genFileInStream);
	
	val initCallFieldsString = getLineString(genFileInStream);
	val initCallFields = getFields(initCallFieldsString);
		(* val debug = printList initCallFields; *)
	
	val paramNameFieldsString = getLineString(genFileInStream);
	val paramNameFields = getFields(paramNameFieldsString);
			
	val paramCount = length(initCallFields)-1;
	
	(* Todo test/throw exception if needed *)

	val paramValueLists = getParamLists genFileInStream paramCount;
	
	val allParamSeqs = allseqs paramValueLists;
	
			(* val debug = printList (hd allParamSeqs); *)
	
	(* getting the test videos *)
		val videoListFileInStream = TextIO.openIn(videoListName);
		val videoList = hd (getParamLists videoListFileInStream 1); (* video list should be on one line *)
		

(* Opening file to write out *)

	(* val outStream = TextIO.openOut(className ^ "Test.autotest.cpp");
	*)
	val outStream = TextIO.openOut(outputFileName);

(* write out part of file before cases *)

	val progunit = TextIO.output( outStream,
					List.nth(templateFields,0) ^ className
					^ List.nth(templateFields,1) ^ className
					^ List.nth(templateFields,2) ^ className
					^ List.nth(templateFields,3)
					);

(* write out cases *)

	val cases = getCases(className, templateCaseStatementFields, initCallFields, allParamSeqs);
	val progunit = writeList outStream cases;
	
(* write out part of file after cases but before tests *)
	val progunit = TextIO.output( outStream,
					templateBetweenCasesAndTestsField);
					

					
(* write out tests *)
	val tests = getTests( className, paramNameFields, templateTestFields, allParamSeqs, videoList );
	val progunit = writeList outStream tests;	








(* Writing out and cleaning up *)
	val progunit = TextIO.closeOut(outStream);
	
	val progunit = TextIO.closeIn(templateFileInStream);
	val progunit = TextIO.closeIn(genFileInStream);
	val progunit = TextIO.closeIn(videoListFileInStream);



(* NOT IN USE

	(* NOT USED
		combining params and video lists 		/*/)
		val paramAndVideoLists = paramValueLists @ videoList;
		val allParamAndVideoSeqs = allseqs paramAndVideoLists;
		*)

val progunit = TextIO.output(outStream,className ^ "\n")

val outStream = TextIO.openOut("mosmltestlog.txt");

val progunit = TextIO.output(outStream,classname ^ "\n");

val progunit = TextIO.closeOut(outStream);

load "CommandLine";
load "List";

val x = TextIO.print classname;

val openOut = openOut("mosmltestlog.txt");

	fun interleavePrint outStream [] [] = ()
	 |  interleavePrint outStream (x::xs) [] = TextIO.output(outStream,x)
	 |  interleavePrint outStream [] (y::ys) = TextIO.output(outStream,y)
	 |	interleavePrint outStream (x::xs) (y::ys) =
		( TextIO.output(outStream,x);  TextIO.output(outStream,y) );


fun writeCases outStream initCallFields paramValueCombinations =
	map (fn paramValueCombination => interleavePrint outStream initCallFields paramValueCombination) paramValueCombinations;


*)